﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;

namespace SystemHelperLibrary.ThreadingExt
{
	public class MyFileWriter : IDisposable
	{
		public event EventHandler<WriteCompletedArgs> Completed;
		public event EventHandler<WriteCompletedArgs> Canceled;
		public event EventHandler<System.ComponentModel.ProgressChangedEventArgs> Changed;

		private static object m_syncLock = new object();
		private MemoryStream ms = null;
		private const int MAX_FILE_SIZE = 14000000;
		private const Int32 buffSize = 1024;
		private byte[] buffer = new byte[buffSize * 8];
		private string m_fpath;
		private bool isBusy = false;
		private System.Threading.CancellationTokenSource cts = null;

		public byte[] DataToWrite
		{
			get
			{
				ms = ms ?? new MemoryStream();
				return ms.ToArray();
			}
			set
			{
				if (ms != null && ms.Length > 0)
					ms.SetLength(0);
				ms = new MemoryStream(value);
			}
		}

		public bool IsBusy
		{
			get
			{
				lock (m_syncLock)
				{
					return isBusy;
				}
			}
		}

		public string PathToFile
		{
			get { return m_fpath; }
			set { m_fpath = value; }
		}

		public MyFileWriter()
		{
			cts = new System.Threading.CancellationTokenSource();
		}

		public MyFileWriter(string fPath)
			: this(fPath, null)
		{
		}

		public MyFileWriter(string fPath, byte[] data)
		{
			m_fpath = fPath;
			DataToWrite = data;
			ms = new MemoryStream(data);
			cts = new System.Threading.CancellationTokenSource();
		}

		public void Cancel()
		{
			if (isBusy)
				cts.Cancel();
		}

		public void WriteSync()
		{
			isBusy = true;
			using (FileStream fs = new FileStream(m_fpath, FileMode.OpenOrCreate))
			{
				int bytesRead = 0;
				while (bytesRead != DataToWrite.Length)
				{
					bytesRead += ms.Read(buffer, 0, buffer.Length);
					fs.Write(buffer, 0, buffer.Length);
				}
			}
			isBusy = false;
		}

		public void WriteAsync()
		{
			if (ms == null || string.IsNullOrEmpty(m_fpath))
				return;

			int overall = (int)ms.Length;
			isBusy = true;
			int bytesRead = 0;
			int read = 0;
			ms.Seek(0, SeekOrigin.Begin);

			Task task = new Task(() =>
			{
				cts.Token.ThrowIfCancellationRequested();
				ClearFile();

				using (FileStream fs = new FileStream(m_fpath, FileMode.OpenOrCreate))
				{
					while (bytesRead != overall)
					{
						if (cts.IsCancellationRequested)
						{
							fs.Flush();
							fs.Close();
							this.OnCancelled(m_fpath, false, true);
							ClearFile();
							isBusy = false;
							cts.Token.ThrowIfCancellationRequested();
						}

						read = ms.Read(this.buffer, 0, buffer.Length);
						System.Threading.Interlocked.Add(ref bytesRead, read);

						lock (m_syncLock)
						{
							fs.Write(buffer, 0, read);
							OnChanged(bytesRead, overall);
							System.Threading.Monitor.Pulse(m_syncLock);
						}
					}
					fs.Flush();
				}
				isBusy = false;
			}, cts.Token, TaskCreationOptions.LongRunning);
			task.Start();

			Task Succeed = task.ContinueWith((t) =>
			{
				isBusy = false;
				this.WriteSyncComp(m_fpath, true, false, null);
			}, TaskContinuationOptions.OnlyOnRanToCompletion);

			Task Cancelled = task.ContinueWith((t) =>
			{
				isBusy = false;
				this.WriteSyncComp(m_fpath, false, true, null);
			}, TaskContinuationOptions.OnlyOnCanceled);

			Task Faulted = task.ContinueWith((t) =>
			{
				isBusy = false;
				var excep = t.Exception == null ? null : t.Exception.InnerException;
				this.WriteSyncComp(m_fpath, false, false, excep);
			}, TaskContinuationOptions.OnlyOnFaulted);

		}

		/// <summary>
		/// 
		/// </summary>
		/// <example>
		/// Task<string> t = GetFileStringAsync(path);          
		///try
		///{
		///     Console.WriteLine(t.Result.Substring(0, 500));
		///}
		///catch (AggregateException ae)
		///{
		///    Console.WriteLine(ae.InnerException.Message);
		///}         
		/// </example>
		/// <param name="path"></param>
		/// <returns></returns>
		public Task<string> GetFileStringAsync(string path)
		{
			FileInfo fi = new FileInfo(path);
			byte[] data = null;
			data = new byte[fi.Length];

			FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, true);

			//Task<int> returns the number of bytes read
			Task<int> task = Task<int>.Factory.FromAsync(
					fs.BeginRead, fs.EndRead, data, 0, data.Length, null);

			// It is possible to do other work here while waiting
			// for the antecedent task to complete.
			// ...

			// Add the continuation, which returns a Task<string>. 
			return task.ContinueWith((antecedent) =>
			{
				fs.Close();

				// Result = "number of bytes read" (if we need it.)
				if (antecedent.Result < 100)
				{
					return "Data is too small to bother with.";
				}
				else
				{
					// If we did not receive the entire file, the end of the
					// data buffer will contain garbage.
					if (antecedent.Result < data.Length)
						Array.Resize(ref data, antecedent.Result);

					// Will be returned in the Result property of the Task<string>
					// at some future point after the asynchronous file I/O operation completes.
					return new UTF8Encoding().GetString(data);
				}
			});
		}

		public Task<string> GetMultiFileData(string[] filesToRead)
		{
			FileStream fs;
			Task<string>[] tasks = new Task<string>[filesToRead.Length];
			byte[] fileData = null;
			for (int i = 0; i < filesToRead.Length; i++)
			{
				fileData = new byte[0x1000];
				fs = new FileStream(filesToRead[i], FileMode.Open, FileAccess.Read, FileShare.Read, fileData.Length, true);

				// By adding the continuation here, the 
				// Result of each task will be a string.
				tasks[i] = Task<int>.Factory.FromAsync(
						 fs.BeginRead, fs.EndRead, fileData, 0, fileData.Length, null)
						 .ContinueWith((antecedent) =>
						 {
							 fs.Close();

							 // If we did not receive the entire file, the end of the
							 // data buffer will contain garbage.
							 if (antecedent.Result < fileData.Length)
								 Array.Resize(ref fileData, antecedent.Result);

							 // Will be returned in the Result property of the Task<string>
							 // at some future point after the asynchronous file I/O operation completes.
							 return new UTF8Encoding().GetString(fileData);
						 });
			}

			// Wait for all tasks to complete. 
			return Task<string>.Factory.ContinueWhenAll(tasks, (data) =>
			{
				// Propagate all exceptions and mark all faulted tasks as observed.
				Task.WaitAll(data);

				// Combine the results from all tasks.
				StringBuilder sb = new StringBuilder();
				foreach (var t in data)
				{
					sb.Append(t.Result);
				}
				// Final result to be returned eventually on the calling thread.
				return sb.ToString();
			});
		}

		private void WriteSyncComp(string file, bool succeed, bool cancel, Exception exc)
		{
			EventHandler<WriteCompletedArgs> handler = System.Threading.Interlocked.CompareExchange(ref Completed, null, null);
			if (handler != null)
			{
				handler(this, new WriteCompletedArgs(file, succeed, cancel, exc));
			}
		}

		private void OnCancelled(string file, bool succeed, bool cancel)
		{
			EventHandler<WriteCompletedArgs> handler = System.Threading.Interlocked.CompareExchange(ref Canceled, null, null);
			if (handler != null)
			{
				handler(this, new WriteCompletedArgs(file, succeed, cancel, null));
			}
		}

		private void OnChanged(int bytesWriten, int totalBytes)
		{
			EventHandler<System.ComponentModel.ProgressChangedEventArgs> handler = System.Threading.Interlocked.CompareExchange(ref Changed, null, null);
			if (handler != null)
			{
				handler(this, new System.ComponentModel.ProgressChangedEventArgs(((bytesWriten * 100) / totalBytes), null));
			}
		}

		private void ClearFile()
		{
			if (System.IO.File.Exists(m_fpath))
				System.IO.File.Delete(m_fpath);
		}

		public void Dispose()
		{
			if (cts != null)
				cts.Dispose();
			this.m_fpath = null;
			m_syncLock = null;
			if (ms != null)
			{
				ms.Close();
				ms.Dispose();
			}

			GC.Collect();
			GC.SuppressFinalize(this);
		}
	}

	public class MyFileCopier : IDisposable
	{
		public event EventHandler<CopyCompletedArgs> Completed;
		public event EventHandler<CopyChangedArgs> Changed;

		private const Int32 buffSize = 1024;
		private string m_fpath, m_dest;
		private static object m_syncLock = new object();
		private bool isBusy = false;
		private bool isCancelled = false;
		private bool isDelete = false;
		private System.Threading.CancellationTokenSource cts = null;
		private byte[] buffer;

		public Exception AsyncException
		{
			get;
			private set;
		}

		public bool IsBusy
		{
			get { return isBusy; }
		}

		public bool IsCancelled
		{
			get { return isCancelled; }
		}

		public string SourceFile
		{
			get { return m_fpath; }
			set { m_fpath = value; }
		}

		public string DestinationFile
		{
			get { return m_dest; }
			set { m_dest = value; }
		}

		public MyFileCopier()
		{
			AsyncException = null;
			m_fpath = string.Empty;
			m_dest = string.Empty;
			isDelete = false;
			cts = new System.Threading.CancellationTokenSource();
		}

		public MyFileCopier(string source, string dest)
			: this(source, dest, false)
		{

		}

		public MyFileCopier(string source, string dest, bool deleteExisting)
		{
			AsyncException = null;
			m_fpath = source;
			m_dest = dest;
			isDelete = deleteExisting;
			cts = new System.Threading.CancellationTokenSource();

			FileInfo fi = new FileInfo(m_fpath);
			if (!fi.Exists)
				throw new FileNotFoundException(string.Format("File {0} not founded", m_fpath));
			if ((fi.Length / 1024L / 1024L) > 150)
			{
				buffer = new byte[20480];
			}
			else
			{
				buffer = new byte[2048];
			}
		}

		public void Cancel()
		{
			lock (m_syncLock)
			{
				if (isBusy)
					cts.Cancel();
			}
		}

		public void CopyAsync()
		{
			Task main = Task.Factory.StartNew(() =>
			{
				cts.Token.ThrowIfCancellationRequested();

				lock (m_syncLock)
				{
					isBusy = true;
					if (isDelete && File.Exists(m_dest))
						File.Delete(m_dest);
				}

				int bytesReaded = 0;
				long percentage = 0;
				using (FileStream fs = new FileStream(m_fpath, FileMode.Open))
				{
					using (FileStream fs2 = new FileStream(m_dest, FileMode.CreateNew))
					{
						while (bytesReaded != fs.Length)
						{
							if (cts.IsCancellationRequested)
							{
								isCancelled = true;
								fs.Close();
								fs2.Flush();
								fs2.Close();
								this.DeleteDestination();
								cts.Token.ThrowIfCancellationRequested();
							}

							int read = fs.Read(buffer, 0, buffer.Length);
							System.Threading.Interlocked.Add(ref bytesReaded, read);
							fs2.Write(buffer, 0, read);

							System.Threading.Interlocked.Exchange(ref percentage, ((bytesReaded * 100) / fs.Length));
							this.CopyChanged(percentage);
						}
						fs2.Flush();
					}
				}
				lock (m_syncLock)
				{
					if (!isCancelled)
						isBusy = false;
				}
			},
			cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

			Task succeed = main.ContinueWith((Tmain) =>
			{
				isBusy = false;
				if (Tmain.Exception != null)
					AsyncException = Tmain.Exception.InnerException;

				CopySyncComp(m_fpath, m_dest, !isCancelled, isCancelled);
			}, cts.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

			Task cancelled = main.ContinueWith((Tmain) =>
			{
				isBusy = false;
				if (Tmain.Exception != null)
					AsyncException = Tmain.Exception.InnerException;

				CopySyncComp(m_fpath, m_dest, false, true);
			}, TaskContinuationOptions.OnlyOnCanceled | TaskContinuationOptions.ExecuteSynchronously);

			Task faulted = main.ContinueWith((Tmain) =>
			{
				isBusy = false;
				if (Tmain.Exception != null)
					AsyncException = Tmain.Exception.InnerException;

				CopySyncComp(m_fpath, m_dest, false, true);
			}, cts.Token, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());

		}

		private void CopySyncComp(string file, string dest, bool succeed, bool cancel)
		{
			EventHandler<CopyCompletedArgs> handler = System.Threading.Interlocked.CompareExchange(ref Completed, null, null);
			if (handler != null)
			{
				handler(this, new CopyCompletedArgs(file, dest, succeed, cancel, this.AsyncException));
			}
		}

		private void CopyChanged(long percentage)
		{
			EventHandler<CopyChangedArgs> handler = System.Threading.Interlocked.CompareExchange(ref Changed, null, null);
			if (handler != null)
			{
				handler(this, new CopyChangedArgs(percentage));
			}
		}

		void DeleteDestination()
		{
			File.Delete(this.m_dest);
		}

		public void Dispose()
		{
			if (cts != null)
				cts.Dispose();

			m_syncLock = null;
			GC.Collect();
			GC.SuppressFinalize(this);
		}
	}

	public class MyFileReader : IDisposable
	{
		public event EventHandler<ReadCompletedArgs> Completed;
		public event EventHandler<ReadChangedArgs> ProgressChanged;

		private System.Threading.CancellationTokenSource cts = null;
		private byte[] BytesToReadBuff = new byte[10240];
		private string m_fileToRead = string.Empty;
		private bool isBusy = false;
		private bool isCancelled = false;
		private static object m_syncLock = new object();

		public bool IsBusy
		{
			get { return isBusy; }
		}

		public bool IsCancelled
		{
			get { return isCancelled; }
		}

		public MyFileReader(string file)
		{
			if (string.IsNullOrEmpty(file) || !File.Exists(file))
			{
				throw new ArgumentException("File doesnt exist", "file");
			}

			m_fileToRead = file;
			cts = new System.Threading.CancellationTokenSource();
		}

		public MyFileReader()
		{
			m_fileToRead = string.Empty;
			cts = new System.Threading.CancellationTokenSource();
		}

		public string FileToRead
		{
			get { return m_fileToRead; }
			set { m_fileToRead = value; }
		}

		public void Cancel()
		{
			cts.Cancel();
		}

		public void ReadAsync()
		{
			List<byte> data = null;
			lock (m_syncLock)
			{
				isBusy = true;
				isCancelled = false;
				data = new List<byte>();
			}

			Task main = Task.Factory.StartNew(() =>
			{
				cts.Token.ThrowIfCancellationRequested();
				long readedBytes = 0;

				using (FileStream fs = new FileStream(m_fileToRead, FileMode.Open, FileAccess.Read))
				{
					while (readedBytes != fs.Length)
					{
						if (cts.IsCancellationRequested)
						{
							isBusy = false;
							isCancelled = true;
							fs.Close();
							cts.Token.ThrowIfCancellationRequested();
						}

						System.Threading.Interlocked.Add(ref readedBytes, fs.Read(BytesToReadBuff, 0, BytesToReadBuff.Length));

						lock (BytesToReadBuff.SyncRoot)
						{
							data.AddRange(BytesToReadBuff);
							System.Threading.Monitor.Pulse(BytesToReadBuff.SyncRoot);
						}

						OnChanged(System.Threading.Interlocked.Read(ref readedBytes));
					}
				}
			}, cts.Token);


			Task succeed = main.ContinueWith((Tmain) =>
			{
				isBusy = false;
				OnCompleted(Tmain.IsCanceled, data.ToArray(), null);
				if (data.Count > 0)
				{
					data.Clear();
					data = null;
				}
			},
				TaskContinuationOptions.OnlyOnRanToCompletion);

			Task cancel = main.ContinueWith((Tmain) =>
			{
				isBusy = false;
				OnCompleted(true, data.ToArray(), null);
				if (data.Count > 0)
				{
					data.Clear();
					data = null;
				}
			},
			TaskContinuationOptions.OnlyOnCanceled);

			Task fail = main.ContinueWith((Tmain) =>
			{
				isBusy = false;
				if (Tmain.Exception != null)
					OnCompleted(Tmain.IsCanceled, data.ToArray(), Tmain.Exception.InnerException);
				if (data.Count > 0)
				{
					data.Clear();
					data = null;
				}
			},
			TaskContinuationOptions.OnlyOnFaulted);
		}

		private void OnCompleted(bool isCancel, byte[] data, Exception exc)
		{
			EventHandler<ReadCompletedArgs> handler = System.Threading.Interlocked.CompareExchange(ref this.Completed, null, null);
			if (handler != null)
				handler(this, new ReadCompletedArgs(exc, data, isCancel));
		}

		private void OnChanged(long readed)
		{
			EventHandler<ReadChangedArgs> handler = System.Threading.Interlocked.CompareExchange(ref this.ProgressChanged, null, null);
			if (handler != null)
				handler(this, new ReadChangedArgs(readed));
		}

		public void Dispose()
		{
			GC.Collect();
			GC.SuppressFinalize(this);
			cts.Dispose();
			m_syncLock = null;
		}
	}

	public class WriteCompletedArgs : EventArgs
	{
		public WriteCompletedArgs()
			: base()
		{
		}

		public WriteCompletedArgs(string filePath, bool succeed, bool cancelled)
			: this(filePath, succeed, cancelled, null)
		{ }

		public WriteCompletedArgs(string filePath, bool succeed, bool cancelled, Exception ex)
			: base()
		{
			IsSucceed = succeed;
			FilePath = filePath;
			Canceled = cancelled;
			AsyncException = ex;
		}

		public bool IsSucceed
		{
			get;
			set;
		}

		public bool Canceled
		{
			get;
			set;
		}

		public string FilePath
		{
			get;
			set;
		}

		public Exception AsyncException
		{
			get;
			private set;
		}
	}

	public class CopyCompletedArgs : EventArgs
	{
		public CopyCompletedArgs()
			: base()
		{
		}

		public CopyCompletedArgs(string source, string dest, bool succeed, bool cancelled, Exception ex)
			: base()
		{
			FileDest = dest;
			IsSucceed = succeed;
			FileSource = source;
			Canceled = cancelled;
			AsyncException = ex;
		}

		public bool IsSucceed
		{
			get;
			set;
		}

		public bool Canceled
		{
			get;
			set;
		}

		public string FileSource
		{
			get;
			set;
		}

		public string FileDest
		{
			get;
			set;
		}

		public Exception AsyncException
		{
			get;
			set;
		}
	}

	public class CopyChangedArgs : EventArgs
	{
		public CopyChangedArgs()
			: base()
		{
		}

		public CopyChangedArgs(long percentage)
			: base()
		{
			Percentage = percentage;
		}

		public long Percentage
		{
			get;
			set;
		}
	}

	public class ReadCompletedArgs : EventArgs
	{
		public ReadCompletedArgs()
			: base()
		{
		}

		public ReadCompletedArgs(Exception ex, byte[] readed, bool isCancel)
			: base()
		{
			IsCancelled = isCancel;
			ReadedData = readed;
			AsyncException = ex;
		}

		public Exception AsyncException
		{
			get;
			private set;
		}

		public byte[] ReadedData
		{
			get;
			private set;
		}

		public bool IsCancelled
		{
			get;
			private set;
		}
	}

	public class ReadChangedArgs : EventArgs
	{
		public ReadChangedArgs()
			: base()
		{
		}

		public ReadChangedArgs(long readedBytes)
			: base()
		{
			ReadedBytes = readedBytes;
		}

		public long ReadedBytes
		{
			get;
			private set;
		}
	}

	public class ProgressReporter : IProgress<long>
	{
		private long m_val;
		public void Report(long value)
		{
			m_val = value;
			Console.WriteLine("{0} Bytes downloaded", value);
		}

		public long Value
		{
			get
			{
				return m_val;
			}
		}
	}

	/// <summary>
	/// Generic interface that can be passed to various functions for
	/// being notified upon progress. Useful for e.g. pumping Windows 
	/// messages during lengthy tasks.
	/// </summary>
	public interface IProgress
	{
		/// <summary>
		/// Called when [progress].
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="System.EventArgs"/>
		/// instance containing the event data.</param>
		void OnProgress(object sender, EventArgs args);
	}

	public interface IProgress<T>
	{
		void Report(T val);
	}

	/// <summary>
	/// Cancel mode
	/// </summary>
	public enum CancelMode
	{
		Continue,
		Cancel
	}

	public delegate CancelMode Cancelable(object userState);

	/// <summary>
	/// Generic interface that can be passed to various functions for
	/// being notified upon progress. Useful for e.g. pumping Windows 
	/// messages during lengthy tasks.
	/// </summary>
	public interface ICancelableProgress
	{
		/// <summary>
		/// Return TRUE to continue, FALSE to cancel.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="System.EventArgs"/>
		/// instance containing the event data.</param>
		/// <returns></returns>
		CancelMode OnProgress(object sender, EventArgs args);
	}
}
